package fr.cdesire.sudoku.applet.bean;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import fr.cdesire.sudoku.applet.exception.GrilleTropValeursException;
import fr.cdesire.sudoku.applet.exception.SudokuAppletException;
import fr.cdesire.sudoku.applet.exception.TechnicalException;
import fr.cdesire.sudoku.applet.utils.GrilleManager;
import fr.cdesire.sudoku.core.commons.Case;
import fr.cdesire.sudoku.core.commons.Grille;
import fr.cdesire.sudoku.core.enums.Valeur;

public class GrilleBean extends Observable implements Serializable, Observer {
	
	/**
	 * Serial ID
	 */
	private static final long serialVersionUID = -4494881668473316293L;
	
	private List<CaseBean> cases;
	
    private BiMap<String, Valeur> valeurs;

	public GrilleBean() {
		cases = new ArrayList<CaseBean>();
		valeurs = HashBiMap.create();
	}
	
	public void update(String json) throws SudokuAppletException {
		try {
			
			this.clear();
			
			JSONParser parser = new JSONParser();
			JSONObject grilleJson = (JSONObject)parser.parse(json);
			JSONArray casesJson = (JSONArray)grilleJson.get("cases");
			
			Set<String> valeursDifferentes = new HashSet<String>();
			for (int i = 0; i<casesJson.size(); i++) {
				
				JSONObject celluleJson = (JSONObject)casesJson.get(i);
				CaseBean caseBean = new CaseBean();
				caseBean.setLigne(((Long)celluleJson.get("ligne")).intValue());
				caseBean.setColonne(((Long)celluleJson.get("colonne")).intValue());
				caseBean.setValeur((String)celluleJson.get("valeur"));
				caseBean.setInitiale(((Boolean)celluleJson.get("initiale")).booleanValue());
				cases.add(caseBean);
				valeursDifferentes.add(caseBean.getValeur());
			}

			associerValeurs(valeursDifferentes);
			setChanged();
			notifyObservers(cases);
			
		} catch (ParseException pe) {
			throw new TechnicalException(pe);
		}
	}
	
	private void associerValeurs(Set<String> valeursDifferentes) throws GrilleTropValeursException {
		
		valeursDifferentes.remove("");
		if (valeursDifferentes.size() > GrilleManager.getInstance().getFormatGrille().getNbCasesCoteGrille()) {
			throw new GrilleTropValeursException();
		}
		
		this.valeurs.clear();		
		Iterator<String> iteValeurs = valeursDifferentes.iterator();
		for (Valeur valeur : Valeur.values()) {
			if (iteValeurs.hasNext()) {
				this.valeurs.put(iteValeurs.next(), valeur);
			} else {
				break;
			}
		}
	}
	
	public void clear() {
		this.cases.clear();
	}

	public List<CaseBean> getCases() {
		return cases;
	}

	public void setCases(List<CaseBean> cases) {
		this.cases = cases;
	}

	public BiMap<String, Valeur> getValeurs() {
		return valeurs;
	}

	public void setValeurs(BiMap<String, Valeur> valeurs) {
		this.valeurs = valeurs;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((cases == null) ? 0 : cases.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		GrilleBean other = (GrilleBean) obj;
		if (cases == null) {
			if (other.cases != null)
				return false;
		} else if (!cases.equals(other.cases))
			return false;
		return true;
	}

	@SuppressWarnings("unchecked")
	@Override
	public String toString() {
		JSONArray casesJson = new JSONArray();

		for (CaseBean cellule : this.cases) {

			JSONObject celluleJson = new JSONObject();

			celluleJson.put("ligne", new Integer(cellule.getLigne()));
			celluleJson.put("colonne", new Integer(cellule.getColonne()));
			celluleJson.put("valeur", cellule.getValeur());
			celluleJson.put("initiale", new Boolean(cellule.isInitiale()));
			casesJson.add(celluleJson);
		}

		JSONObject grilleJson = new JSONObject();
		grilleJson.put("cases", casesJson);
		
		return grilleJson.toJSONString();
	}

	@Override
	public void update(Observable observable, Object object) {
		
		if (observable instanceof GrilleManager) {
			
			this.clear();
			
			for (Case cellule : ((Grille)object).getCases().values()) {
				CaseBean caseBean = new CaseBean();
				caseBean.setLigne(cellule.getPosition().getLigne());
				caseBean.setColonne(cellule.getPosition().getColonne());
				caseBean.setValeur(valeurs.inverse().get(
						cellule.getValeur()));
				caseBean.setInitiale(((Grille)object).getValeursRenseignees()
						.containsKey(cellule.getPosition()));
				cases.add(caseBean);
			}
		}
	}
}
